using System;
using System.Collections.Generic;
using System.ComponentModel;
using Microsoft.Ccr.Core;
using Microsoft.Dss.Core.Attributes;
using Microsoft.Dss.ServiceModel.Dssp;
using Microsoft.Dss.ServiceModel.DsspServiceBase;
using W3C.Soap;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Emgu.CV;
using Emgu.CV.UI;
using Emgu.Util;
using Emgu.CV.Structure;
using submgr = Microsoft.Dss.Services.SubscriptionManager;
using webcam = Microsoft.Robotics.Services.WebCam.Proxy;

namespace Robotics.ImageCapt
{
    [Contract(Contract.Identifier)]
    [DisplayName("ImageCapt")]
    [Description("ImageCapt service (no description provided)")]
    class ImageCaptService : DsspServiceBase
    {
        /// <summary>
        /// Service state
        /// </summary>
        [ServiceState]
        [InitialStatePartner(Optional = true, ServiceUri = "ImageCapt.Config.xml")]
        ImageCaptState _state = new ImageCaptState();

        /// <summary>
        /// Main service port
        /// </summary>
        [ServicePort("/ImageCapt", AllowMultipleInstances = true)]
        ImageCaptOperations _mainPort = new ImageCaptOperations();

        /// <summary>
        /// Internal port to update private state.
        /// </summary>
        private ImageCaptOperations _internalPort = new ImageCaptOperations();

        [Partner("WebCam", Contract = webcam.Contract.Identifier, CreationPolicy = PartnerCreationPolicy.UseExistingOrCreate)]
        // Ports for the Web Camera
        webcam.WebCamOperations _webCamPort = new webcam.WebCamOperations();
        webcam.WebCamOperations _webCamNotify = new webcam.WebCamOperations();
        Port<Shutdown> _webCamShutdown = new Port<Shutdown>();

        [SubscriptionManagerPartner]
        submgr.SubscriptionManagerPort _submgrPort = new submgr.SubscriptionManagerPort();

        /// <summary>
        /// Service constructor
        /// </summary>
        public ImageCaptService(DsspServiceCreationPort creationPort)
            : base(creationPort)
        {
        }

        /// <summary>
        /// Service start
        /// </summary>
        protected override void Start()
        {

            // 
            // Add service specific initialization here
            // 

            base.Start();

            if (_state == null)
            {
                _state = new ImageCaptState();
            }

            if ((_state.ImgInputType < 0) || (_state.ImgInputType > 1))
            {
                _state.ImgInputType = 0;
            }

            if (_state.ImgFileName == null)
            {
                _state.ImgFileName = " ";
            }

            SaveState(_state);

            MainPortInterleave.CombineWith(Arbiter.Interleave(
                new TeardownReceiverGroup
                (
                ),
                new ExclusiveReceiverGroup
                (
                    Arbiter.Receive<NotifyImageCapture>(true, _internalPort, ImageCaptureHandler)
                ),
                new ConcurrentReceiverGroup
                (
                    Arbiter.ReceiveWithIterator<webcam.UpdateFrame>(true, _webCamNotify, WebCamUpdateFrameHandler)
                )
            ));

            if (_state.ImgInputType == 0)
            {
                SpawnIterator(ConnectWebCam);
            }
            else if (_state.ImgInputType == 1)
            {
                SpawnIterator(ReadFromFile);
            }
        }

        public void ImageCaptureHandler(NotifyImageCapture update)
        {
            update.ResponsePort.Post(DefaultUpdateResponseType.Instance);
            SendNotification(_submgrPort, update);
            Console.WriteLine("Image from camera notification");
            LogInfo("Image from camera notification");
        }

        // Handler for connecting to WebCam
        IEnumerator<ITask> ConnectWebCam()
        {
            //ServiceInfoType info = null;
            Fault fault = null;
            SubscribeResponseType s;
            //String camera = Opt.Service;

            // Subscribe to the webcam
            webcam.Subscribe subscribe = new webcam.Subscribe();
            subscribe.NotificationPort = _webCamNotify;
            subscribe.NotificationShutdownPort = _webCamShutdown;

            _webCamPort.Post(subscribe);

            yield return Arbiter.Choice(
                //_webCamPort.Subscribe(_webCamNotify),
                subscribe.ResponsePort,
                delegate(SubscribeResponseType success)
                { s = success; },
                delegate(Fault f)
                {
                    fault = f;
                }
            );

            if (fault != null)
            {
                LogError(null, "Failed to subscribe to webcam", fault);
                yield break;
            }

            yield break;

        }

        IEnumerator<ITask> ReadFromFile()
        {
            Image<Bgr, byte> img = new Image<Bgr, byte>(_state.ImgFileName);
            Bitmap bmp = img.Bitmap;

            _internalPort.Post(new NotifyImageCapture(new CaptureResult(bmp)));

            yield break;
        }

        // Handler for new frames from the camera
        IEnumerator<ITask> WebCamUpdateFrameHandler(webcam.UpdateFrame update)
        {
            webcam.QueryFrameResponse frame = null;
            Fault fault = null;

            // Throw away the backlog
            // This does no harm because we are throwing away notifications,
            // not webcam images
            Port<webcam.UpdateFrame> p = (Port<webcam.UpdateFrame>)_webCamNotify[typeof(webcam.UpdateFrame)];
            if (p.ItemCount > 2)
            {
                Console.WriteLine("Webcam backlog: " + p.ItemCount);
                p.Clear();
            }

            webcam.QueryFrame query = new webcam.QueryFrame();
            // Set a timeout so that this cannot wait forever
            query.TimeSpan = TimeSpan.FromMilliseconds(1000);
            _webCamPort.Post(query);

            // Wait for response
            yield return Arbiter.Choice(
                query.ResponsePort,
                delegate(webcam.QueryFrameResponse success)
                {
                    frame = success;
                },
                delegate(Fault f)
                {
                    fault = f;
                }
            );

            if (fault != null)
            {
                LogError(null, "Failed to get frame from camera", fault);
                yield break;
            }

            // Create a bitmap from the webcam response and display it
            Bitmap bmp = MakeBitmap(frame.Size.Width, frame.Size.Height, frame.Frame);

            //SpawnIterator<Bitmap>(bmp, DisplayImage);
            _internalPort.Post(new NotifyImageCapture(new CaptureResult(bmp)));

            yield break;
        }

        Choice PerformShutdown(ref Port<Shutdown> port)
        {
            Shutdown shutdown = new Shutdown();
            if (port != null)
            {
                port.Post(shutdown);
            }
            port = null;

            return Arbiter.Choice(
                shutdown.ResultPort,
                delegate(SuccessResult success) { },
                delegate(Exception e)
                {
                    LogError(e);
                }
            );
        }

        Bitmap MakeBitmap(int width, int height, byte[] imageData)
        {
            // NOTE: This code implicitly assumes that the width is a multiple
            // of four bytes because Bitmaps have to be longword aligned.
            // We really should look at bmp.Stride to see if there is any padding.
            // However, the width and height come from the webcam and most cameras
            // have resolutions that are multiples of four.

            Bitmap bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);

            BitmapData data = bmp.LockBits(
                new Rectangle(0, 0, bmp.Width, bmp.Height),
                ImageLockMode.WriteOnly,
                PixelFormat.Format24bppRgb
            );

            Marshal.Copy(imageData, 0, data.Scan0, imageData.Length);

            bmp.UnlockBits(data);

            return bmp;
        }

        // Display an image
        IEnumerator<ITask> DisplayImage(Bitmap bmp)
        {
            Image<Bgr, byte> img = new Image<Bgr, byte>(bmp);
            CvInvoke.cvNamedWindow("h");
            CvInvoke.cvShowImage("h", img);
            CvInvoke.cvWaitKey(0);
            yield break;
        }

        /// <summary>
        /// Handles Subscribe messages
        /// </summary>
        /// <param name="subscribe">the subscribe request</param>
        [ServiceHandler]
        public void SubscribeHandler(Subscribe subscribe)
        {
            SubscribeHelper(_submgrPort, subscribe.Body, subscribe.ResponsePort);
        }
    }
}


